import { Component, NgZone } from '@angular/core';
import { NavController, NavParams, AlertController } from 'ionic-angular';
import { ToastController } from 'ionic-angular';
import { BLE } from '@ionic-native/ble';
import CryptoJS from 'crypto-js';
import * as encoding from 'text-encoding';
import { CryptoHelper } from './cryptohelper'
import { scheduleMicroTask } from '@angular/core/src/util';
import { Console } from '@angular/core/src/console';
import { Platform } from 'ionic-angular';
// Bluetooth UUIDs
const LIGHTBULB_SERVICE = 'ff10';
const SWITCH_CHARACTERISTIC = 'ff11';
const DIMMER_CHARACTERISTIC = 'ff12';
// NeoPixel Service UUIDs
const NEOPIXEL_SERVICE = 'ccc0';
const COLOR = 'ccc1';
const BRIGHTNESS = 'ccc2';
const POWER_SWITCH = 'ccc3';

const THERMOMETER_SERVICE = 'bbb0';
const TEMPERATURE_CHARACTERISTIC = 'bbb1';

const TELINK_SERVICE = '00010203-0405-0607-0809-0a0b0c0d1910';

const TELINK_CHARACTERISTIC_PAIR = '00010203-0405-0607-0809-0a0b0c0d1914';

const TELINK_CHARACTERISTIC_COMMAND = '00010203-0405-0607-0809-0a0b0c0d1912';

const TELINK_CHARACTERISTIC_NOTIFY = '00010203-0405-0607-0809-0a0b0c0d1911';

//const TELINK_CHARACTERISTIC_OTA = '00010203-0405-0607-0809-0a0b0c0d1913';
//const TELINK_CHARACTERISTIC_OTA_IOS = '1913';
@Component({
  selector: 'page-detail',
  templateUrl: 'detail.html',
})



export class DetailPage {
  power: boolean;
  autoE4: boolean;
  auto: boolean;
  startNotification: boolean;
  brightness: number;
  peripheral: any = {};
  temperature: number;
  statusMessage: string;
  status: string;
  sessionkey: Uint8Array;
  loginbuff: Uint8Array;
  cryptohelper: CryptoHelper;
  pnetworkname: Uint8Array;
  ppassword: Uint8Array;
  prandm: Uint8Array;
  prands: Uint8Array;
  macAddrByte: Uint8Array;
  sn: number;
  ison: number;

  constructor(public navCtrl: NavController,
    public navParams: NavParams,
    private ble: BLE,
    private toastCtrl: ToastController,
    private alertCtrl: AlertController,
    private ngZone: NgZone,
    public plt: Platform) {
    this.ison = 1;
    this.sn = 1;
    this.cryptohelper = new CryptoHelper();
    let device = navParams.get('device');

    this.sessionkey = new Uint8Array(16);
    this.loginbuff = new Uint8Array(17);
    this.loginbuff[0] = 12;
    this.prandm = new Uint8Array(8);
    for (let i = 0; i < 8; i++) {
      this.prandm[i] = i
    }

    this.pnetworkname = new Uint8Array(16);
    this.ppassword = new Uint8Array(16);
    for (let i = 0; i < 16; i++) {
      this.pnetworkname[i] = 0;
      this.ppassword[i] = 0;
    }

    let encoder = new encoding.TextEncoder("utf8");
    let tmpNetworkName = encoder.encode("telink_mesh1");
    let tmpPassword = encoder.encode("123");
    for (let i = 0; i < tmpNetworkName.length; i++) {
      this.pnetworkname[i] = tmpNetworkName[i];
      this.ppassword[i] = tmpPassword[i]
    }

    let sk = new Uint8Array(16);
    let d = new Uint8Array(16);
    let rs = new Uint8Array(16);
    for (let i = 0; i < 16; i++) {
      d[i] = this.pnetworkname[i] ^ this.ppassword[i];
    }
    for (let i = 0; i < 8; i++) {
      sk[i] = this.prandm[i];
    }
    for (let i = 8; i < 16; i++) {
      sk[i] = 0;
    }
    let buffer = this.myencryptAES(sk, d);
    for (let i = 1; i < 9; i++) {
      this.loginbuff[i] = this.prandm[i - 1];
    }


    for (let i = 9; i < 17; i++) {
      this.loginbuff[i] = buffer[i - 9];
    }
    this.setStatus('start Connecting to ' + device.name || device.id);
    this.ble.connect(device.id).subscribe(
      peripheral => this.onConnected(peripheral),
      peripheral => this.onDeviceDisconnected(peripheral)
    );
  }

  copyarray(src, srcshift, des, desshift, length) {
    for (let i = 0; i < length; i++) {
      des[desshift + i] = src[srcshift + i];
    }
  }

  reverse2(inarray, start, end) {
    let outarray = new Uint8Array(inarray.length);
    for (let i = 0; i < inarray.length; i++) {
      outarray[i] = inarray[i];
    }
    for (let i = 0; i <= (end - start); i++) {
      outarray[end - i] = inarray[start + i];
    }
    return outarray;
  }

  reverse(inarray) {

    let outarray = new Uint8Array(inarray.length);
    for (let i = 0; i < inarray.length; i++) {
      outarray[i] = inarray[inarray.length - 1 - i];
    }
    return outarray;
  }

  onConnected(peripheral) {

    this.ngZone.run(() => {
      this.setStatus('success Connected to ' + (peripheral.name || peripheral.id));
      this.peripheral = peripheral;
      this.peripheral.Service
    });

    if (this.plt.is("ios")) {
      console.log("ios type" + this.peripheral.advertising.KCBMAC);
      let tmp = this.getMacAddrssByte(this.peripheral.advertising.KCBMAC);
      tmp.reverse();
      this.macAddrByte = new Uint8Array(tmp.length)
      this.copyarray(tmp, 0, this.macAddrByte, 0, tmp.length);
      console.log("====================================");
      for (let i = 0; i < this.macAddrByte.length; i++) {
        console.log(this.macAddrByte[i]);
      }
      console.log("====================================");
    } else if (this.plt.is("android")) {
      console.log("android type");
      let tmp = this.getMacAddrssByte(this.peripheral.id);
      this.macAddrByte = new Uint8Array(tmp.length)
      this.copyarray(tmp, 0, this.macAddrByte, 0, tmp.length);

    } else {
      console.log("error, not supported device type!");
    }




    this.ble.write(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_PAIR, this.loginbuff.buffer).then(
      () => this.setStatus('write data to lights success'),
      e => this.showAlert('Unexpected Error', 'Error  write to light')
    );
    // Update the UI with the current state of the switch characteristic

    this.ble.read(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_PAIR).then(
      buffer => {

        let data = new Uint8Array(buffer);

        console.log('switch characteristic ' + data[0]);
        if (data[0] == 13) {
          console.log("write randm to light success");
          let sk = new Uint8Array(16);
          this.prands = new Uint8Array(8);
          for (let i = 1; i < 17; i++) {
            sk[i - 1] = data[i];
          }
          for (let i = 1; i < 9; i++) {
            this.prands[i - 1] = data[i];
          }
          let key = new Uint8Array(16);
          this.copyarray(this.prands, 0, key, 0, this.prands.length);
          let plaintex = new Uint8Array(16);
          for (let i = 0; i < 16; i++) {
            plaintex[i] = this.pnetworkname[i] ^ this.ppassword[i];
          }
          let ciphertextbuffer = this.myencryptAES(key, plaintex);
          let result = new Uint8Array(16);
          this.copyarray(this.prands, 0, result, 0, this.prands.length);
          this.copyarray(ciphertextbuffer, 0, result, 8, 8);
          //result=this.reverse2(result,8,15);
          if (result[0] == sk[0]) {
            console.log("confire rands success");
          } else {
            console.log("comfire rands failed");
          }
          this.copyarray(this.prandm, 0, key, 0, this.prandm.length);
          this.copyarray(this.prands, 0, key, 8, this.prands.length);
          let msessionkey = this.myencryptAES(plaintex, key);
          //msessionkey=this.reverse2(msessionkey,0,msessionkey.length-1);//68,-57,108,85,81
          for (let i = 0; i < 16; i++) {
            this.sessionkey[i] = msessionkey[i];
          }
          this.setStatus("sessionkey:" + this.ToHexString(this.sessionkey));
          this.status = "connected";
          //send command;
          //this.testEncrypt();

          // this.ble.startNotification(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY)
          // .subscribe(
          //   data => this.notifydata(data)
          //   , () => this.showAlert('Unexpected Error', 'enable notify failed')
          // );
        }
      }
    );



    // // Update the UI with the current state of the dimmer characteristic
    // this.ble.read(this.peripheral.id, LIGHTBULB_SERVICE, DIMMER_CHARACTERISTIC).then(
    //   buffer => {
    //     let data = new Uint8Array(buffer);
    //     console.log('dimmer characteristic ' + data[0]);
    //     this.ngZone.run(() => {
    //       this.brightness = data[0];
    //     });
    //   }
    // );

    //this.testDecrypt();
    //this.testDecrypt();
  }
  notifydata(buffer) {
    let data = new Uint8Array(buffer);
    console.log('Received Notification:   = ' + data);

    if(data.length==0){return;}

    let iv = this.getSecIVS();
    this.copyarray(data, 0, iv, 3, 5);
    let ret = this.mydecryptCmd(this.sessionkey, iv, data);

    this.setStatus('Received Notification:   = ' + this.ToHexString(ret));
  }
  nextSn() {
    let maxNum = 0xffffff;
    if (this.sn > maxNum) {
      this.sn = 1;
    }
    this.sn++;
    return this.sn;
  }

  myencrpytCmd(sn, cmd, iv, key) {
    let mic = new Uint8Array(2);
    let ps = new Uint8Array(15);
    this.copyarray(cmd, 3, mic, 0, 2);
    this.copyarray(cmd, 5, ps, 0, 15);
    let cipher = this.aes_ccm_encrypt(sn, key, iv, mic, 2, ps, 15);

    return cipher;

  }

  mydecryptCmd(sessionkey, iv, data) {
    let mic = new Uint8Array(2);
    this.copyarray(data, 5, mic, 0, 2);
    let ps = new Uint8Array(13);
    this.copyarray(data, 7, ps, 0, 13);
    return this.aes_ccm_decrypt(sessionkey, iv, mic, 2, ps, 13);
  }

  myencryptAES(key, plaintex) {
    plaintex = this.reverse(plaintex);
    key = this.reverse(key);
    let key_array = this.cryptohelper.convertUint8ArrayToWordArray(key);
    let plaintex_array = this.cryptohelper.convertUint8ArrayToWordArray(plaintex);
    let encrypted = CryptoJS.AES.encrypt(plaintex_array, key_array, {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.NoPadding,
    });
    let ciphertextbuffer = this.cryptohelper.convertWordArrayToUint8Array(encrypted.ciphertext);

    return ciphertextbuffer.reverse();
  }

  aes_ccm_encrypt(sn, key, iv, mic, mic_len, ps, len) {

    let e = new Uint8Array(16);
    let r = new Uint8Array(16);
    this.mymemset(e, 0, 16);
    this.copyarray(iv, 0, r, 0, 8);
    r[8] = len;
    r = this.myencryptAES(key, r);
    //r=this.reverse(tmp);
    for (let i = 0; i < len; i++) {
      r[i & 15] ^= ps[i];
      if (((i & 15) == 15) || (i == len - 1)) {
        r = this.myencryptAES(key, r);
        //r=this.reverse(tmp);
      }
    }
    for (let i = 0; i < mic_len; i++) {
      mic[i] = r[i];
    }
    this.mymemset(r, 0, 16);
    this.copyarray(iv, 0, r, 1, 8);
    for (let i = 0; i < len; i++) {
      if ((i & 15) == 0) {
        e = this.myencryptAES(key, r);
        //e=this.reverse(tmp);
        r[0]++;
      }
      ps[i] ^= e[i & 15];
    }

    let ret = new Uint8Array(20);
    this.copyarray(this.getByteArrayFrom32Int(sn), 0, ret, 0, 3);
    this.copyarray(mic, 0, ret, 3, 2);
    this.copyarray(ps, 0, ret, 5, 15);
    return ret;
    //return sn[0-2] mic[3-4] ps[5-19]
  }
  mymemset(array, data, len) {
    for (let i = 0; i < len; i++) {
      array[i] = data;
    }
  }
  aes_ccm_decrypt(key, iv, mic, mic_len, ps, len) {

    let e = new Uint8Array(16);
    let r = new Uint8Array(16);
    this.mymemset(r, 0, 16);
    this.copyarray(iv, 0, r, 1, 8);
    for (let i = 0; i < len; i++) {
      if ((i & 15) == 0) {
        e = this.myencryptAES(key, r);
        r[0]++;
      }
      ps[i] ^= e[i & 15];
    }
    this.mymemset(r, 0, 16);
    this.copyarray(iv, 0, r, 0, 8);
    r[8] = len;
    r = this.myencryptAES(key, r);
    for (let i = 0; i < len; i++) {
      r[i & 15] ^= ps[i];
      if (((i & 15) == 15) || (i == (len - 1))) {
        r = this.myencryptAES(key, r);
      }
    }
    for (let i = 0; i < mic_len; i++) {
      if (mic[i] != r[i]) {
        console.log("failed in aes_ccm_decrypt")
      }
    }
    //
    let ret = new Uint8Array(20);
    //0-4 iv 5-6 mic 7-19 ps
    this.copyarray(iv, 3, ret, 0, 5);
    this.copyarray(mic, 0, ret, 5, 2);
    this.copyarray(ps, 0, ret, 7, 15);
    return ret;

  }

  buildCommand(dstMeshAddr, sn, opcode, param) {//dstaddr=1 opcode=0xD0 sn=3 


    if (this.ison == 1) {
      this.ison = 0;
    } else {
      this.ison = 1;
    }

    let command = new Uint8Array(20);
    let offset = 0;

    command[offset++] = sn & 0xff;
    command[offset++] = sn >> 8 & 0xff;
    command[offset++] = sn >> 16 & 0xff;

    command[offset++] = 0;
    command[offset++] = 0;

    command[offset++] = (dstMeshAddr & 0xFF);
    command[offset++] = (dstMeshAddr >> 8 & 0xFF);

    command[offset++] = (opcode | 0xC0);

    let vendorId = 4354;
    command[offset++] = (vendorId >> 8 & 0xFF);
    command[offset++] = (vendorId & 0xFF);//10个字节参数

    this.copyarray(param, 0, command, offset, param.length);

    return command;

  }
  getMacAddrssByte(macaddress) {
    console.log("macAddr" + macaddress);
    let strArray = macaddress.split(":");
    let macByte = new Uint8Array(strArray.length);
    for (let i = 0; i < strArray.length; i++) {
      macByte[i] = parseInt(strArray[i], 16) & (0xff);
    }
    macByte.reverse();
    return macByte;
  }

  getByteArrayFrom32Int(data) {
    let byteArray = new Uint8Array(4);
    byteArray[0] = data & 0xff;
    byteArray[1] = (data >> 8) && 0xff;
    byteArray[2] = (data >> 16) && 0xff;
    byteArray[3] = (data >> 24) && 0xff;
    return byteArray;
  }


  getSecIVM(sn) {
    let ivm = new Uint8Array(8);
    this.copyarray(this.macAddrByte, 0, ivm, 0, 4);
    ivm[4] = 0x01;
    ivm[5] = sn & 0xff;
    ivm[6] = (sn >> 8) & 0xff;
    ivm[7] = (sn >> 16) & 0xff;
    return ivm;
  }

  getSecIVS() {
    let ivs = new Uint8Array(8);
    ivs[0] = this.macAddrByte[0];
    ivs[1] = this.macAddrByte[1];
    ivs[2] = this.macAddrByte[2];

    return ivs;
  }

  success() {
    console.log("write data to blelight success");
  }

  fail() {
    console.log("write data to blelight failed");
  }

  showAlert(title, message) {
    let alert = this.alertCtrl.create({
      title: title,
      subTitle: message,
      buttons: ['OK']
    });
    alert.present();
  }

  onDeviceDisconnected(peripheral) {
    let toast = this.toastCtrl.create({
      message: 'The peripheral unexpectedly disconnected',
      duration: 3000,
      position: 'middle'
    });
    toast.present();
  }

  // Disconnect peripheral when leaving the page
  ionViewWillLeave() {
    console.log('ionViewWillLeave disconnecting Bluetooth');
    this.ble.stopNotification(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY);
    this.ble.disconnect(this.peripheral.id).then(
      () => console.log('Disconnected ' + JSON.stringify(this.peripheral)),
      () => console.log('ERROR disconnecting ' + JSON.stringify(this.peripheral))
    )
  }

  setStatus(message) {
    console.log(message);
    this.ngZone.run(() => {
      this.statusMessage = message;
    });
  }

  onPowerSwitchChange(event) {

    if (this.status != "connected") { return; }

    let param = new Uint8Array(3);
    if (this.ison == 1) {
      param[0] = 0x01;
      param[1] = 0x00;
      param[2] = 0x00;
    } else {
      param[0] = 0x00;
      param[1] = 0x00;
      param[2] = 0x00;
    }
    let sn = this.nextSn();
    let iv = new Uint8Array(8);
    iv = this.getSecIVM(sn);
    let cmd = this.buildCommand(0xffff, sn, 0xD0, param);
    let encryptcmd = this.myencrpytCmd(sn, cmd, iv, this.sessionkey);
    this.ble.writeWithoutResponse(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_COMMAND, encryptcmd.buffer).then(
      () => this.setStatus('write cmd to lights:' + this.ToHexString(encryptcmd)),
      e => this.showAlert('Unexpected Error', 'Error  write cmd to light' + e)
    );
  }
  test() {
    let param = new Uint8Array(3);
    if (this.ison == 1) {
      param[0] = 0x01;
      param[1] = 0x00;
      param[2] = 0x00;
    } else {
      param[0] = 0x00;
      param[1] = 0x00;
      param[2] = 0x00;
    }
    let sn = 1;
    let iv = new Uint8Array(8);
    let sessionkey = new Uint8Array(16);
    this.mymemset(sessionkey, 1, 16);
    this.mymemset(iv, 2, 8);
    let cmd = this.buildCommand(0xffff, sn, 0xD0, param);
    let encryptcmd = this.myencrpytCmd(sn, cmd, iv, sessionkey);
    console.log("test encryptcmd" + encryptcmd);
  }

  onE4SwitchChange(event) {

    if (this.status != "connected") { return; }
    let param = new Uint8Array(0);
    let sn = this.nextSn();
    let iv = new Uint8Array(8);
    iv = this.getSecIVM(sn);
    let cmd = this.buildCommand(0xffff, sn, 0xE4, param);
    let encryptcmd = this.myencrpytCmd(sn, cmd, iv, this.sessionkey);
    this.ble.write(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_COMMAND, encryptcmd.buffer).then(
      () => this.setStatus('write cmd to lights:' + this.ToHexString(encryptcmd)),
      e => this.showAlert('Unexpected Error', 'Error  write cmd to light' + e)
    );
  }

  onAutoConnectSwitchChange(event) {

    let buff = new Uint8Array(1);
    buff[0] = 1;
    
    // for (let i = 0; i < 3; i++) {
    //   this.ble.write(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY, buff.buffer).then(
    //     () => this.setStatus('write data to enable notify'),
    //     e => this.showAlert('Unexpected Error write data to enable notify', 'write data to enable notify')
    //   );
    // }
    this.ble.startNotification(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY)
      .subscribe(
      data => this.notifydata(data)
      , () => this.showAlert('Unexpected Error enable notify failed', 'enable notify failed')
      );
     // this.ble.read(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_PAIR).then(buff=>{console.log("notificaiton read")});
    //      buff[0] = 1;
    // for (let i = 0; i < 1; i++) {
    //   this.ble.write(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY, buff.buffer).then(
    //     () => this.setStatus('write data to enable notify'),
    //     e => this.showAlert('Unexpected Error write data to enable notify', 'write data to enable notify')
    //   );
    // }
  }

  onWriteNotificationCommand(event) {
    let buff = new Uint8Array(1);

    buff[0] = 1;
    for (let i = 0; i < 1; i++) {
      this.ble.write(this.peripheral.id, TELINK_SERVICE, TELINK_CHARACTERISTIC_NOTIFY, buff.buffer).then(
        () => this.setStatus('write data to enable notify'),
        e => this.showAlert('Unexpected Error write data to enable notify', 'write data to enable notify')
      );
    }
  }

  getUUID() {
    if (this.plt.is("ios")) {

    } else if (this.plt.is("android")) {


    } else {
      console.log("error, not supported device type!");
    }

  }

  ToHexString(arr) {
    let s: string;
    s = "";
    for (let i = 0; i < arr.length; i++) {
      s = s + arr[i].toString(16) + " ";
    }
    return s;

  }
}

